package com.assistant.tasks.thread;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.assistant.common.Constants;
import com.assistant.common.exception.file.InvalidExtensionException;
import com.assistant.common.utils.HttpClient;
import com.assistant.common.utils.StringUtils;
import com.assistant.tasks.mapper.TasksMapper;
import com.assistant.tasks.pojo.TaskDTO;
import com.assistant.tasks.service.impl.TasksServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.poi.ss.usermodel.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.LocalDateTime;
import java.util.*;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * @Classname PredictThread
 * @Description 用于处理上传的ZIP文件，解压其中的Excel文件，进行数据处理，并将结果压缩成ZIP文件上传至服务器
 * @Date 2025/3/6 14:35
 * @Created by xxx
 */
@Slf4j
@EnableAsync
public class PredictThread implements Runnable {
    private static String fileDir1;
    private TaskDTO taskDTO;
    private TasksMapper tasksMapper;
    private TasksServiceImpl tasksService;
    @Value("${file.request-url}")
    private String requestUrl;

    public PredictThread(TaskDTO taskDTO, String fileDir1, TasksMapper tasksMapper, TasksServiceImpl tasksService) {
        this.taskDTO = taskDTO;
        this.fileDir1 = fileDir1;
        this.tasksMapper = tasksMapper;
        this.tasksService = tasksService;
    }

    public static void unzip(Path zipPath, Path outputDir) throws IOException {
        try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(zipPath))) {
            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                Path targetPath = outputDir.resolve(entry.getName()).normalize();

                if (!targetPath.startsWith(outputDir)) {
                    throw new IOException("Invalid zip entry: " + entry.getName());
                }

                if (entry.isDirectory()) {
                    Files.createDirectories(targetPath);
                } else {
                    Files.createDirectories(targetPath.getParent());
                    try (OutputStream os = Files.newOutputStream(targetPath)) {
                        byte[] buffer = new byte[1024];
                        int len;
                        while ((len = zis.read(buffer)) > 0) {
                            os.write(buffer, 0, len);
                        }
                    }
                }
            }
        }
    }

    // 处理上传的ZIP文件（Path重构版）
    public static String processUploadedZip(InputStream inputStream, String date, String fileId) throws IOException {
        if (StringUtils.isBlank(date) || StringUtils.isBlank(fileId)) {
            throw new IllegalArgumentException("日期和文件ID不能为空");
        }
        Path zipDir = null;
        Path outputDir = null;
        // 1. 创建存储目录（使用NIO防止路径遍历漏洞）
        zipDir = Paths.get(fileDir1, "zip", fileId).normalize().toAbsolutePath();
        Files.createDirectories(zipDir);
        // 2. 保存ZIP文件（安全文件名 + 异常捕获）
        String fileName = date + Constants.ZIP;
        Path zipPath = Paths.get(String.valueOf(zipDir), fileName).normalize().toAbsolutePath();
        File file = zipPath.toFile();
        FileUtils.copyInputStreamToFile(inputStream, file);
        log.info("文件上传成功，文件名：{}", fileName);
        // 3. 创建解压目录（独立try块防止污染）
        outputDir = Paths.get(fileDir1, "unzipped", fileId).normalize().toAbsolutePath();
        Files.createDirectories(outputDir);
        // 4. 解压文件（增加解压校验）
        unzip(zipPath, outputDir);
        if (Files.list(outputDir).count() == 0) {
            throw new IOException("ZIP文件解压后为空");
        }
        // 处理Excel文件（使用NIO遍历）
        Files.walk(outputDir)
                .filter(path -> Files.isRegularFile(path) && isExcelFile(path))
                .forEach(excelPath -> processExcel(excelPath));

        // 压缩结果文件
        Path resultPath = Paths.get(fileDir1, date + Constants.ZIP);
        Files.createDirectories(resultPath.getParent());
        compressFolder(outputDir, resultPath);


        return String.valueOf(resultPath.toAbsolutePath());
    }

    // 压缩目录（优化版）
    public static void compressFolder(Path sourceDir, Path targetZip) throws IOException {
        try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(targetZip), StandardCharsets.UTF_8)) {
            zos.setMethod(ZipOutputStream.DEFLATED);
            zos.setLevel(Deflater.BEST_COMPRESSION);

            Files.walkFileTree(sourceDir, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    String entryName = sourceDir.relativize(file).toString().replace(File.separatorChar, '/');
                    zos.putNextEntry(new ZipEntry(entryName));
                    Files.copy(file, zos);
                    zos.closeEntry();
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                    if (!dir.equals(sourceDir)) {
                        String entryName = sourceDir.relativize(dir).toString().replace(File.separatorChar, '/') + "/";
                        zos.putNextEntry(new ZipEntry(entryName));
                        zos.closeEntry();
                    }
                    return FileVisitResult.CONTINUE;
                }
            });
        }
    }

    // 辅助方法：判断Excel文件
    private static boolean isExcelFile(Path path) {
        String fileName = path.getFileName().toString().toLowerCase();
        return fileName.endsWith(".xls") || fileName.endsWith(".xlsx");
    }

    // 辅助方法：处理Excel文件
    private static void processExcel(Path excelPath) {
        try (InputStream in = Files.newInputStream(excelPath)) {
            Workbook workbook = WorkbookFactory.create(in);
            for (int s = 0; s < workbook.getNumberOfSheets(); s++) {
                Sheet sheet = workbook.getSheetAt(s);
                DataFormatter dataFormatter = new DataFormatter();
                //TODO 存储结果
                List<String> result = new ArrayList<>();
                for (int i = 1; i <= sheet.getLastRowNum(); i++) {
                    Row row = sheet.getRow(i);
                    if (row == null || row.getCell(0) == null) continue;
                    Object[] rowData = new Object[row.getLastCellNum()];
                    for (Cell cell : row) {
                        rowData[cell.getColumnIndex()] = dataFormatter.formatCellValue(cell);
                    }
                    log.info("Row data: {}", Arrays.toString(rowData));
                    if (i % Constants.MAX_ROWS == 0) {
                        // TODO 模型
                    }

                    Cell pcdnCell = row.getCell(Constants.PCDN_CELL);
                    if (pcdnCell == null) {
                        pcdnCell = row.createCell(Constants.PCDN_CELL);
                    }
                    setCellValue(pcdnCell, "已处理");
                }
                int index = 0;
                for (Row row : sheet) {
                    if (index >= result.size()) {
                        break;
                    }
                    if (row == null || row.getCell(0) == null) continue;
                    Cell pcdnCell = row.getCell(Constants.PCDN_CELL);
                    if (pcdnCell == null) {
                        pcdnCell = row.createCell(Constants.PCDN_CELL);
                    }
                    setCellValue(pcdnCell, result.get(index));
                    index++;
                }
                try (FileOutputStream fos = new FileOutputStream(excelPath.toFile())) {
                    workbook.write(fos);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Error processing Excel: " + excelPath, e);
        }
    }

    private static void setCellValue(Cell cell, Object value) {
        if (value == null) {
            cell.setCellValue((String) null);
            return;
        }
        if (value instanceof Number) {
            cell.setCellValue(((Number) value).doubleValue());
        } else if (value instanceof Boolean) {
            cell.setCellValue((Boolean) value);
        } else if (value instanceof java.util.Date) {
            cell.setCellValue((java.util.Date) value);
        } else {
            cell.setCellValue(value.toString());
        }
    }

    @Async
    @Override
    public void run() {
        try {
            String resultPath = processUploadedZip(taskDTO.getInputStream(), taskDTO.getDate(), taskDTO.getFileId());
            taskDTO.setFilePath(resultPath);
            taskDTO.setState(Constants.STATE_SUCCESS);
            taskDTO.setStateTime(LocalDateTime.now());
            tasksMapper.update(taskDTO);

            TaskDTO taskDTO1 = new TaskDTO();
            taskDTO1.setDate(taskDTO.getDate());
            taskDTO1.setState(Constants.STATE_SUCCESS);
            taskDTO1.setCategory(Constants.CATEGORY_ANALYSIS);

            int count = tasksMapper.countTask(taskDTO1);
            if (count != 0) {
                taskDTO1.setState(Constants.STATE_PENDING);
                taskDTO1.setSource(Constants.CREATE_SYSTEM);
                taskDTO1.setLocalFile(new File(resultPath));
                taskDTO1.setContainsPcdn(1);
                taskDTO1.setInputStream(taskDTO.getInputStream());
                tasksService.createTask(taskDTO1);
            } else {
                Map<String, File> fileParams = new HashMap<>();
                fileParams.put("file", new File(resultPath));
                Map<String, String> formParams = new HashMap<>();
                formParams.put("fileId", taskDTO.getFileId());
                String result = HttpClient.doPostWithFiles(Constants.U_URL, formParams, fileParams);
                JSONObject resultBody = JSON.parseObject(result);
                String resultCode = resultBody.getString("code");
                if (resultCode == null || !resultCode.equals("0")) {
                    log.error("上传文件失败：{}", result);
                    taskFail("上传文件失败");
                }
                taskDTO.getInputStream().close();
            }
        } catch (IOException e) {
            log.error("处理文件异常", e);
            taskFail("处理文件异常");
        } catch (InvalidExtensionException e) {
            log.error("无效的文件扩展名", e);
            taskFail("无效的文件扩展名");
        } catch (Exception e) {
            log.error("其他异常", e);
            taskFail("其他异常");
        }
    }

    private void taskFail(String message) {
        taskDTO.setState(Constants.STATE_FAIL);
        taskDTO.setStateTime(LocalDateTime.now());
        taskDTO.setMsg(message);
        tasksMapper.update(taskDTO);
    }
}
